#include <stdio.h>
#include <time.h>
#include <string.h>
#include <limits.h>
#include <float.h>
#include <stdlib.h>
#include "liblpa.h"

#define SHOW_TIME stdTime = ((double)clock() - start) / CLOCKS_PER_SEC; if (verbosity) printf("%.3fs\t", stdTime);
#define SHOW_TIME_LPA lpaTime = ((double)clock() - start) / CLOCKS_PER_SEC; if (verbosity) printf("%.3fs (%.0f%%)\t", lpaTime, lpaTime/stdTime*100);

#define testRunnerInner(funcname, operation, vartype, lpaFunc, lpaMask, safe, preleft)               \
    char test##funcname(void) {                                                             \
        vartype *left = malloc(i*1024*1024*sizeof(vartype));                                \
        vartype *right = malloc(i*1024*1024*sizeof(vartype));                               \
        vartype *result = malloc(i*1024*1024*sizeof(vartype));                              \
        if (left==NULL || right==NULL || result==NULL) {                                    \
            printf("Could not allocate memory!");                                           \
            return -1;                                                                      \
        }                                                                                   \
        for (j=0; j<i*1024*1024; ++j) {                                                     \
            left[j] = (vartype)rand();                                                      \
            right[j] = (vartype)rand();                                                     \
        }                                                                                   \
        if (safe == 1) {                                                                    \
            for (j=0; j<i*1024*1024; ++j)                                                   \
                if (right[j] == 0)                                                          \
                    right[j] = 1;                                                           \
        }                                                                                   \
                                                                                            \
        start = clock();                                                                    \
        for (j=0; j<i*1024*1024; ++j) {                                                     \
            result[j] = preleft left[j] operation right[j];                                         \
        }                                                                                   \
        SHOW_TIME;                                                                          \
                                                                                            \
        start = clock();                                                                    \
        lpaFunc(left, right, i*1024*1024, sizeof(*left) lpaMask);                           \
        SHOW_TIME_LPA;                                                                      \
                                                                                            \
        if (memcmp(left, result, i*1024*1024*sizeof(*left)) != 0)                           \
            printf("Results are not coherent in %s! lpa error occured!\n", #funcname);      \
        free(left); free(right); free(result);                                              \
        return 0;                                                                           \
    }

#define IntegerDeclarations(funcname, operation, lpaFunc, safe,p)                             \
    testRunnerInner(funcname##UC, operation, unsigned char, lpaFunc, , safe,p)                \
    testRunnerInner(funcname##US, operation, unsigned short, lpaFunc, , safe,p)               \
    testRunnerInner(funcname##UL, operation, unsigned long, lpaFunc, , safe,p)                \
    testRunnerInner(funcname##ULL, operation, unsigned long, lpaFunc, , safe,p)               \
    testRunnerInner(funcname##SC, operation, signed char, lpaFunc, , safe,p)                  \
    testRunnerInner(funcname##SS, operation, signed short, lpaFunc, , safe,p)                 \
    testRunnerInner(funcname##SL, operation, signed long, lpaFunc, , safe,p)                  \
    testRunnerInner(funcname##SLL, operation, signed long, lpaFunc, , safe,p)                 \

#define testRunner(funcname, operation, lpaFunc, safe)                                      \
    IntegerDeclarations(funcname, operation, lpaFunc, safe,)                                 \
    testRunnerInner(funcname##F, operation, float, lpaFunc, | LPA_FLOAT, safe,)              \
    testRunnerInner(funcname##D, operation, double, lpaFunc, | LPA_FLOAT, safe,)             \

unsigned int i, j;
unsigned short verbosity;
clock_t start;
float stdTime, lpaTime;

testRunner(Addition, +, lpaAdd, 0)
testRunner(Substraction, -, lpaSub, 0)
testRunner(Multiplication, *, lpaMul, 0)
testRunner(Division, /, lpaDiv, 1)
IntegerDeclarations(And, &, lpaAnd, 0,)
IntegerDeclarations(AndNot, &, lpaAndNot, 0,~)
IntegerDeclarations(Or, |, lpaOr, 0,)
IntegerDeclarations(XOr, ^, lpaXOr, 0,)

char testCmpTrue() {
    unsigned int *left = malloc(i*1024*1024*sizeof(unsigned int));
    unsigned int *right = malloc(i*1024*1024*sizeof(unsigned int));
    unsigned int *result = malloc(i*1024*1024*sizeof(unsigned int));
    if (left==NULL || right==NULL || result==NULL) {
        printf("Could not allocate memory!");
        return -1;
    }
    for (j=0; j<i*1024*1024; ++j) {
        left[j] = (unsigned int)rand();
        right[j] = left[j];
    }
    // Test standard
    start = clock();
    result[0] = memcmp(left, right, i*1024*1024 * sizeof(*left));
    SHOW_TIME;

    // Test liblpa
    start = clock();
    left[0] = lpaCmp(left, right, i*1024*1024, sizeof(*left));
    SHOW_TIME_LPA;

    if ((left[0] != 0) != (result[0] != 0))
        printf("\nResults are not coherent in integer comparison! lpa error occured!\n");
    free(left); free(right); free(result);
    return 0;
}

char testCmpFalse() {
    unsigned int *left = malloc(i*1024*1024*sizeof(unsigned int));
    unsigned int *right = malloc(i*1024*1024*sizeof(unsigned int));
    unsigned int *result = malloc(i*1024*1024*sizeof(unsigned int));
    if (left==NULL || right==NULL || result==NULL) {
        printf("Could not allocate memory!");
        return -1;
    }
    for (j=0; j<i*1024*1024; ++j) {
        left[j] = (unsigned int)rand();
        right[j] = left[j];
    }
    // Test standard
    right[i*1024*1024-1] = ~left[i*1024*1024-1];
    start = clock();
    result[0] = memcmp(left, right, i*1024*1024 * sizeof(*left));
    SHOW_TIME;

    // Test liblpa
    start = clock();
    left[0] = lpaCmp(left, right, i*1024*1024, sizeof(*left));
    SHOW_TIME_LPA;

    if ((left[0] != 0) != (result[0] != 0))
        printf("\nResults are not coherent in integer comparison! lpa error occured!\n");
    free(left); free(right); free(result);
    return 0;
}

#define testIntegerLoop(suffix)\
    if (verbosity)\
        printf("Size\tAdd.\tlpaAdd\t\tSub.\tlpaSub\t\tMul.\tlpaMul\t\tDiv\tlpaDiv\t\tAnd\tlpaAnd\t\t\n");\
    for (i=1; i<=l; i*=2) {\
        if (verbosity)\
            printf("%i Mb:\t", i*4);\
        if (testAddition##suffix() != 0) break;\
        if (testSubstraction##suffix() != 0) break;\
        if (testMultiplication##suffix() != 0) break;\
        if (testDivision##suffix() != 0) break;\
        if (testAnd##suffix() != 0) break;\
        if (testAndNot##suffix() != 0) break;\
        if (testOr##suffix() != 0) break;\
        if (testXOr##suffix() != 0) break;\
\
        if (testCmpTrue() != 0) break;\
        if (testCmpFalse() != 0) break;\
        if (verbosity)\
            printf("\n");\
    }

#define testFloatLoop(suffix)\
    if (verbosity)\
        printf("Size\tAddF.\tlpaAddF\t\t\tSubF.\tlpaSubF\t\t\n");\
    for (i=1; i<=l; i*=2) {\
        if (verbosity)\
            printf("%i Mb:\t", i*4);\
\
        if (testAddition##suffix() != 0) break;\
        if (testSubstraction##suffix() != 0) break;\
        if (testMultiplication##suffix() != 0) break;\
        if (testDivision##suffix() != 0) break;\
\
        printf("\n");\
    }

int main(int argc, char *argv[]) {
    verbosity = 0;
    unsigned int k = 0, l = 1;
    for (k=0; k<argc; ++k) {
        if ((strcmp(argv[k], "-v") == 0))
            verbosity = 1;
        else if ((strcmp(argv[k], "-a") == 0))
            l = -1;
    }

    printf("Unsigned Char benchmark\n~~~~~~~~~~~~~~~~~~~~~~~\n");
    testIntegerLoop(UC)
    printf("Unsigned Short benchmark\n~~~~~~~~~~~~~~~~~~~~~~~~  \n");
    testIntegerLoop(US)
    printf("Unsigned Long benchmark\n~~~~~~~~~~~~~~~~~~~~~~~\n");
    testIntegerLoop(UL)
    printf("Unsigned Long Long benchmark\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
    testIntegerLoop(ULL)
    printf("Signed Char benchmark\n~~~~~~~~~~~~~~~~~~~~~\n");
    testIntegerLoop(SC)
    printf("Signed Short benchmark\n~~~~~~~~~~~~~~~~~~~~~~\n");
    testIntegerLoop(SS)
    printf("Signed Long benchmark\n~~~~~~~~~~~~~~~~~~~~~\n");
    testIntegerLoop(SL)
    printf("Signed Long Long benchmark\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
    testIntegerLoop(SLL)

    printf("Single precision floating point benchmark\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
    testFloatLoop(F)

    printf("Double precision floating point benchmark\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
    testFloatLoop(D)

    return 0;
}
